<template>
  <div>
    <div class="operate">
      <el-upload
        class="upload-file"
        :accept="accept"
        :on-change="handleChange"
        :show-file-list="false"
        :auto-upload="false"
      >
        <el-button class="annexBox" :icon="DocumentAdd">
          {{ btnText }}
        </el-button>
      </el-upload>
      <el-upload
        class="upload-file"
        :accept="accept"
        :http-request="handleFileUpload"
        :show-file-list="false"
      >
        <el-button class="annexBox" :icon="Document">超大附件</el-button>
      </el-upload>
    </div>
    <el-progress v-if="isShowProgress" :percentage="successfulUploads" />
    <div class="upload-file-list">
      <el-button
        v-for="(item, index) in fileList"
        class="file__item"
        :style="{
          borderColor: Screen_Frame_Colour[getIconList(item.icon)],
        }"
      >
        <div class="item-txt">
          <img class="img-icon" :src="getIconImage(item.icon)" />
          <el-tooltip effect="dark" :content="item.fileName" placement="top-start">
            <div class="txt">
              {{ item.fileName }}
            </div>
          </el-tooltip>
        </div>
        <div v-if="isShowUploadBtn" class="mask">
          <el-button
            type="danger"
            :icon="Tickets"
            class="mask-item-btn"
            @click="previewHandler(item, item.fileAddress)"
          />
          <el-button
            type="warning"
            :icon="Delete"
            class="mask-item-btn"
            @click="deleteHandler(item, index)"
          />
        </div>
        <div v-else class="mask">
          <el-button
            type="danger"
            :icon="Tickets"
            class="mask-item-btn"
            @click="previewHandler(item, item.fileAddress)"
          />
          <el-button
            type="primary"
            :icon="Download"
            class="mask-item-btn"
            @click="downloadFile(item.fileAddress)"
          />
        </div>
      </el-button>
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { ElMessage } from 'element-plus';
import type { UploadRequestOptions } from 'element-plus';
import axios from 'axios';
import { queryJson } from '@u/request/index';
import {
  Screen_Frame_Colour,
  getIconList,
  getIconImage,
  previewHandler,
  downloadFile,
} from '@u/file/index';
import { Tickets, Delete, DocumentAdd, Document, Download } from '@element-plus/icons-vue';
import { MergeFileUpload, getMd5, DelFile } from '@/api/file';
import type { FileItem, ChunkFile } from './type/index';

const props = defineProps({
  modelValue: {
    type: Array<FileItem>,
    default: () => [],
  },
  btnText: {
    type: String,
    default: '添加附件',
  },
  // 控制上传文件格式
  accept: {
    type: String,
    default: '*.*',
  },
  // 控制上传按钮是否显示
  isShowUploadBtn: {
    type: Boolean,
    default: true,
  },
});

const emit = defineEmits(['update:modelValue', 'change']);

const token =
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjE2NzY1OTY3MTEyNzczLCJPcmdJZCI6MTc5NSwiQWNjb3VudCI6IuiDoeivl-S8nyIsIlJlYWxOYW1lIjoi6IOh6K-X5LyfIiwiVGVuYW50SWQiOjE0MjQzNjczNDI5OTU3LCJJc1N1cGVyQWRtaW4iOmZhbHNlLCJPcmdhbml6YXRpb24iOiJbe1wiT3JnSURcIjoxNzk1LFwiUm9sZUlEXCI6MTYxNjA5MzM4MzYyMjksXCJSYW5rXCI6MCxcIklzUHJpbWFyeVwiOjEsXCJJc0FkanVuY3RcIjowLFwiSXNPcmdIZWFkXCI6MH1dIiwiaWF0IjoxNzI3MDU4MDU4LCJuYmYiOjE3MjcwNTgwNTgsImV4cCI6MTcyNzEwMTI1OCwiaXNzIjoiVEhQbGF0Zm9ybSIsImF1ZCI6IlRIUGxhdGZvcm0ifQ.Oxj7cwqQTGMKJ-5aCBDH43LyS75pqwr66gDgDpIqIFw';

//附件数组
const fileList = ref<FileItem[]>([]);

watch(
  () => props.modelValue,
  (newVal) => {
    fileList.value = newVal;
  },
  { deep: true, immediate: true },
);

//是否展示进度条
const isShowProgress = ref(false);
//上传进度
const successfulUploads = ref(0);

//切片上传进度
const uploadProgress = ref<number[]>([]); // 用于Vue模板的响应式进度数组
const MAX_CONCURRENT_UPLOADS = 5; //最大并发上传数
const chunkWaitQueue = ref<ChunkFile[]>([]); //切片上传等待队列（达到最大并发上传数时暂停上传，将切片暂存到等待队列）
let activeChunkNum = 0; //正在执行上传操作的文件数
let successChunkNum = 0; //已上传(上传成功)切片数

//定义块大小
let chunkBytes = 1 * 1024 * 1024; //1M

// 选择文件
function handleChange(file: File) {
  let bytesPerPiece = 200 * 1024 * 1024;
  if (file.size > bytesPerPiece) {
    return ElMessage({
      message: `文件大小超过200MB，请使用超大附件按钮上传附件`,
      type: 'warning',
    });
  }
  uploadHandler(file);
}

// 开始附件上传
function uploadHandler(file: any) {
  if (file.status !== 'ready') return;
  //附件名称转成UTF编码
  const encodedName = encodeURIComponent(file.name);

  const uploadFile = new File([file.raw], encodedName, {
    type: file.raw.type,
  });

  const fData = new FormData();

  fData.append('file', uploadFile);
  successfulUploads.value = 0;
  let formDate = {};
  formDate = {
    FileName: uploadFile.name,
    Status: 0,
  };

  isShowProgress.value = true;
  const url = '/api/sysFile/uploadSysFile';

  Submit(url, formDate, fData, (progressEvent) => {
    successfulUploads.value = Math.round((progressEvent.loaded * 100) / progressEvent.total);
  })
    .then((res: any) => {
      fileList.value.push({
        fileId: res.fileId,
        icon: (getFileType(res.fileName) || '') as string,
        fileName: res.fileName,
        fileAddress: res.fileAddress,
        size: res.size,
      });
      if (successfulUploads.value == 100) {
        isShowProgress.value = false;
      }
      emit('update:modelValue', fileList.value);
    })
    .catch(() => {
      isShowProgress.value = false;
      ElMessage({
        message: `该文件上传失败`,
        type: 'warning',
      });
    });
}

// 超大附件上传
const handleFileUpload = async (options: UploadRequestOptions) => {
  const { file } = options;

  //大附件切片数组
  const fileChunkList = [];

  successfulUploads.value = 0;
  //附件名称转成UTF编码
  const encodedName = encodeURIComponent(file.name);
  const uploadFile = new File([file], encodedName, {
    type: file.type,
  });
  // 计算切片数量
  const chunkCount = Math.ceil(uploadFile.size / chunkBytes); // 计算切片数量

  for (let i = 0; i < chunkCount; i++) {
    const start = i * chunkBytes;
    const end = Math.min(start + chunkBytes, uploadFile.size);
    const chunk = uploadFile.slice(start, end);
    const _file = new File([chunk], uploadFile.name, { type: uploadFile.type });

    fileChunkList.push({ index: i + 1, file: _file });
  }
  let icon = getFileType(file.name);
  let md5 = getMd5();

  //开始上传，显示进度条
  isShowProgress.value = true;
  uploadProgress.value = new Array(fileChunkList.length).fill(0); // 初始化进度数组
  fileChunkList.forEach((fileChunk) => {
    startUpload(fileChunk.file, fileChunk.index, md5, encodedName, icon, fileChunkList.length);
  });
};

// 切片附件上传
function startUpload(
  chunkFile: File,
  index: number,
  md5: string,
  fileName: string,
  icon: string,
  fileChunkCount: number,
) {
  if (activeChunkNum >= MAX_CONCURRENT_UPLOADS) {
    chunkWaitQueue.value.push({ chunkFile, index, md5, fileName, icon, fileChunkCount });
    return;
  }

  const fData = new FormData();
  fData.append('file', chunkFile);
  // 开始上传切片
  activeChunkNum++;
  Submit(`/api/sysFile/aliCutFileUpload`, { number: index, md5: md5 }, fData, (progressEvent) => {
    const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
    uploadProgress.value[index] = percentCompleted;
    let totalProgress =
      uploadProgress.value.reduce((acc, curr) => acc + curr, 0) / uploadProgress.value.length;
    totalProgress = Math.round(totalProgress);
    successfulUploads.value = totalProgress;
  })
    .then(() => {
      activeChunkNum--;
      successChunkNum++; // 切片上传成功，增加已上传切片计数
      // 检查是否有更多切片等待上传
      if (chunkWaitQueue.value.length > 0) {
        const nextSlice: any = chunkWaitQueue.value.shift();
        startUpload(
          nextSlice.chunkFile,
          nextSlice.index,
          nextSlice.md5,
          nextSlice.fileName,
          nextSlice.icon,
          nextSlice.fileChunkCount,
        );
      }
      if (successChunkNum === fileChunkCount) {
        // 所有切片全部上传完成后，调用接口整合附件，并返回文件地址
        mergeFile(md5, fileName, icon);
      }
    })
    .catch(() => {
      activeChunkNum--;
      ElMessage({
        message: '文件上传中出现错误,请重新上传',
        type: 'warning',
      });
      isShowProgress.value = false;
    });
  // }
}

// 整合上传
function mergeFile(md5: string, fileName: string, icon: string) {
  let formDate = {};
  formDate = { md5: md5, Status: 0, name: fileName, FileType: icon };
  MergeFileUpload(formDate, {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  })
    .then((res: any) => {
      const { code, result } = res;
      if (res && code === 200) {
        isShowProgress.value = false;
        fileList.value.push({
          fileId: result.fileId,
          icon: (icon || '') as string,
          fileName: decodeURIComponent(result.fileName),
          fileAddress: result.fileAddress,
          size: result.size,
        });
        successfulUploads.value = 100;
        emit('update:modelValue', fileList.value);
      }
    })
    .catch(() => {
      isShowProgress.value = false;
    });
}

// 获取文件类型
function getFileType(icon: string) {
  let arr = icon.lastIndexOf('.');
  const type = icon.substring(arr + 1, icon.length);
  return `.${type}`;
}

// 删除文件
const deleteHandler = (item: any, index: number) => {
  DelFile([item.fileId]).then(() => {
    fileList.value.splice(index, 1);
    ElMessage({
      message: '删除成功',
      type: 'success',
    });
    emit('update:modelValue', fileList.value);
  });
};

//附件提交
function Submit(
  url: string,
  params: Object,
  fileData: FormData,
  onUploadProgress?: (progressEvent: any) => void,
) {
  return axios
    .post(`${url}${queryJson(params)}`, fileData, {
      headers: {
        Authorization: `Bearer ${token}`,
      },
      //上传进度回调
      onUploadProgress: onUploadProgress,
    })
    .then((res) => {
      try {
        return res.data.result;
      } catch (error) {
        throw error;
      }
    })
    .catch((error) => Promise.reject(error));
}
</script>
<style lang="scss">
@import './upload.scss';
</style>
